package at.ac.tuwien.dse.health.geo.util;

import com.google.common.collect.Range;
import org.springframework.data.mongodb.core.geo.Point;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * Provides utilities for geographic computations.
 *
 * @author Bernhard Nickel
 * @author Gregor Schauer
 * @author Dominik Strasser
 */
public class GeoUtils {
	public static final double MILE_FACTOR = 1.609344d;
	// magic number = EARTH_CIRCUMFERENCE = 24872.4d / Degree in circle = 360
	public static final double MAGIC_NUMBER = 60 * 1.1515;
	public static final Range<Double> COORDINATE_RANGE = Range.closed(-180D, 180D);

	private GeoUtils() {
	}

	/**
	 * Calculates the distance between two {@link Point Points} in kilometers using spherical law of cosines formula.
	 *
	 * @return distance between the points in kilometers
	 * @see <a href="http://mathworld.wolfram.com/SphericalTrigonometry.html">
	 *      http://mathworld.wolfram.com/SphericalTrigonometry.html</a>
	 */
	public static double distance(Point p1, Point p2) {
		double lat1 = p1.getX();
		double lat2 = p2.getX();
		double theta = p1.getY() - p2.getY();
		double dist = Math.sin(Math.toRadians(lat1)) * Math.sin(Math.toRadians(lat2))
				+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.cos(Math.toRadians(theta));
		dist = Math.acos(dist);
		dist = Math.toDegrees(dist);
		return dist * MAGIC_NUMBER * MILE_FACTOR;
	}

	/**
	 * Converts the given latitude/longitude coordinates to a point.
	 *
	 * @param latitude the latitude of the coordinate
	 * @param longitude the longitude of the coordinate
	 * @return the resulting point
	 */
	public static Point newPoint(double latitude, double longitude) {
		checkArgument(COORDINATE_RANGE.contains(latitude), "'latitude' must be in the interval [-180, 180]");
		checkArgument(COORDINATE_RANGE.contains(longitude), "'longitude' must be in the interval [-180, 180]");
		return new Point(latitude, longitude);
	}

	/**
	 * Converts the given latitude/longitude coordinates to a point.
	 *
	 * @param location the coordinates
	 * @return the resulting point
	 * @see #newPoint(double, double)
	 */
	public static Point newPoint(double[] location) {
		checkArgument(location.length == 2, "'location' must have exactly two elements");
		return newPoint(location[0], location[1]);
	}

	/**
	 * Converts the given {@link Point} to latitude/longitude coordinates.
	 *
	 * @param point the point to convert
	 * @return the coordinates of the point
	 */
	public static double[] toCoordinates(Point point) {
		return new double[]{point.getX(), point.getY()};
	}
}
